package com.noob.assessment.service.impl;

import com.noob.assessment.domain.dto.LockResultDTO;
import com.noob.assessment.domain.po.FileInfoPO;
import com.noob.assessment.domain.pojo.FileLock;
import com.noob.assessment.service.FileInfoService;
import com.noob.assessment.service.FileLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author: zhengwq
 * datetime: 2020-04-20 14:46
 * description: 文件锁服务 接口实现类
 */
@Service
public class FileLockServiceImpl implements FileLockService {

    /**
     * 锁在缓存中存储的名称
     */
    private static final String LOCK_CACHE_NAME = "locks";

    /**
     * 文件锁有效时长
     */
    private static final int LOCK_ACTIVE_DURATION = 60 * 1000;

    /**
     * 对象锁
     */
    private static final Object LOCK = new Object();

    /**
     * 文件信息服务
     */
    @Autowired
    private FileInfoService fileInfoService;

    /**
     * spring 缓存管理器
     */
    @Autowired
    private CacheManager cacheManager;

    /**
     * 尝试获取文件锁
     * 无论本次加锁是成功或者失败 都会返回当前文件锁情况
     *
     * @param fileId 文件id
     * @param userId 用户id
     * @return 获取锁的结果
     */
    @Override
    public LockResultDTO tryLockFile(int fileId, String userId) {

        // 判断要锁的文件在文件信息表中是否有记录
        FileInfoPO fileInfoPO = fileInfoService.getById(fileId);
        if (fileInfoPO == null) {
            throw new RuntimeException("文件id[" + fileId + "]不存在！");
        }

        // 判断缓存管理器中是否有缓存锁"locks"
        Cache cache = cacheManager.getCache(LOCK_CACHE_NAME);
        if (cache == null) {
            throw new RuntimeException("Cache:[" + LOCK_CACHE_NAME + "] is null");
        }

        // 从缓存中获取文件锁对象
        FileLock fileLock = cache.get(fileId, FileLock.class);
        // 文件没被锁住 准备加锁
        if (fileLock == null) {
            // 双重检查锁 防止多个线程在同一时间点发现锁失效进行锁创建操作 导致覆盖
            synchronized (LOCK) {
                fileLock = cache.get(fileId, FileLock.class);
                if (fileLock == null) {
                    fileLock = FileLock.builder()
                            .fileId(fileId)
                            .holder(userId)
                            .ttl(new AtomicInteger(60))
                            .expireTimestamp(System.currentTimeMillis() + LOCK_ACTIVE_DURATION)
                            .build();
                    cache.put(fileId, fileLock);
                }
            }
        }

        // 构建获取锁的结果对象
        LockResultDTO lockResultDTO = LockResultDTO.builder()
                .holder(fileLock.getHolder())
                .ttl(fileLock.getTtl().get())
                // 记录抢锁是否成功 先初始化为false
                .result(false)
                .build();

        // 文件锁存在并且有效、锁的持有者是自己
        if (fileLock.getExpireTimestamp() >= System.currentTimeMillis() && fileLock.getHolder().equals(userId)) {
            lockResultDTO.setResult(true);
        }

        return lockResultDTO;
    }

}
